﻿/* 
 * Copyright (c) 2009 Intel Corporation
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * -- Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * -- Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * -- Neither the name of the Intel Corporation nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE INTEL OR ITS
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * Contributor: Nathan Letwory
 * 
 */

using System;
using System.Security.Cryptography;
using System.Net;
using System.Threading;
using Mono.Math;
using Mono.Security.Cryptography;

namespace VerseSharp
{
    public class VerseSession : UDPBase
    {
        public event Connect0CommandCallback OnConnect0;
        public event Connect1CommandCallback OnConnect1;
        public event Connect2CommandCallback OnConnect2;
        public event ConnectAcceptCallback OnConnectAccept;
        public event ConnectTerminateCallback OnConnectTerminate;
        public event PacketAckCallback OnPacketAck;
        public event PacketNakCallback OnPacketNak;
        public event NodeIndexSubscribeCallback OnNodeIndexSubscribe;
        public event NodeCreateCallback OnNodeCreate;
		public event NodeDestroyCallback OnNodeDestroy;
        public event NodeNameSetCallback OnNodeNameSet;
        public event GeometryLayerCreateCallback OnGeometryLayerCreate;
        public event GeometryLayerSubscribeCallback OnGeometryLayerSubscribe;
        public event GeometryLayerUnsubscribeCallback OnGeometryLayerUnsubscribe;
        public event GeometryVertexSetXYZReal32Callback OnGeometryVertexSetXYZReal32;
        public event GeometryPolygonSetCornerUInt32Callback OnGeometryPolygonSetCornerUInt32;
        public event GeometryCreaseSetVertexCallback OnGeometryCreaseSetVertex;
        public event GeometryCreaseSetEdgeCallback OnGeometryCreaseSetEdge;
		public event ObjectTransformSubscribeCallback OnObjectTransformSubscribe;
		public event ObjectTransformPosReal32Callback OnObjectTransformPosReal32;
		public event ObjectTransformRotReal32Callback OnObjectTransformRotReal32;
		public event ObjectTransformScaleReal32Callback OnObjectTransformScaleReal32;
		public event ObjectLightSetCallback OnObjectLightSet;
		public event ObjectLinkSetCallback OnObjectLinkSet;
		public event ObjectMethodGroupCreateCallback OnObjectMethodGroupCreate;
		public event ObjectMethodGroupSubscribeCallback OnObjectMethodGroupSubscribe;
		public event ObjectMethodCreateCallback OnObjectMethodCreate;
		public event ObjectMethodDestroyCallback OnObjectMethodDestroy;
		public event ObjectMethodCallCallback OnObjectMethodCall;
		public event ObjectHideCallback OnObjectHide;

        protected long packetID = -1;
        protected string username;
        protected string password;
        protected uint avatarID;
        protected RSAManaged loginRSA;
        protected byte[] xorKey;
        protected bool xorKeySet;

        public VerseSession(IPEndPoint verseServer, string username, string password)
            : base(verseServer)
        {
            this.username = username;
            this.password = password;

            #region Register Callbacks

            OnConnect1 += Connect1Handler;
            OnConnectAccept += ConnectAcceptHandler;
            OnPacketAck += PacketAckHandler;

            #endregion Register Callbacks
        }

        public void Start()
        {
            // Start the UDP subsystem
            base.Start(IPAddress.Any);

            // Generate an RSA keypair, used for login
            loginRSA = new RSAManaged(VerseConstants.ENCRYPTION_KEY_BITS);
            RSAParameters rsaParams = loginRSA.ExportParameters(false);

            // Initialize the XOR key, used for encrypting normal traffic
            xorKey = new byte[VerseConstants.ENCRYPTION_KEY_SIZE];
            xorKeySet = false;

            // Send the stage 0 connect command
            ConnectCommand connect = new ConnectCommand();
            connect.Stage = 0;
            connect.Exponent = rsaParams.Exponent;
            connect.Modulus = rsaParams.Modulus;

            SendCommand(connect);
        }

        public new void Stop()
        {
            // FIXME: Logout

            base.Stop();
        }

        public void SetIndexSubscriptions(NodeTypes mask)
        {
            NodeIndexSubscribeCommand indexSubscribe = new NodeIndexSubscribeCommand();
            indexSubscribe.Mask = mask;
            SendCommand(indexSubscribe);
        }

        public void SetNodeSubscription(uint nodeID, bool subscribe)
        {
            NodeSubscribeCommand nodeSubscribe = new NodeSubscribeCommand();
            nodeSubscribe.NodeID = nodeID;
            nodeSubscribe.Subscribed = subscribe;
            SendCommand(nodeSubscribe);
        }

        public void SetGeometryLayerSubscription(uint nodeID, ushort layerID, RealFormat precision, bool subscribe)
        {
            GeometryLayerSubscribeCommand geometrySubscribe = new GeometryLayerSubscribeCommand();
            geometrySubscribe.NodeID = nodeID;
            geometrySubscribe.LayerID = layerID;
            geometrySubscribe.Precision = precision;
            geometrySubscribe.Subscribed = subscribe;
            SendCommand(geometrySubscribe);
        }

		public void SetObjectTransformSubscription(uint nodeID, RealFormat precision, bool subscribe)
		{
			Console.WriteLine(String.Format("Subscribing to object transformation on {0} with precision {1}", nodeID, precision));
			ObjectTransformSubscribeCommand objectTransformSubscribe = new ObjectTransformSubscribeCommand();
			objectTransformSubscribe.NodeID = nodeID;
			objectTransformSubscribe.Precision = precision;
			objectTransformSubscribe.Subscribed = subscribe;
			SendCommand(objectTransformSubscribe);
		}

        public void SendCommand(IVerseCommand command)
        {
            VersePacket packet = new VersePacket();
            packet.Command = command;

			if (command.ID < CommandID.PacketAck || command.ID > CommandID.PacketNak)
				Console.WriteLine(String.Format("Sending {0}", command.ID));

            // Reset to zero if we've hit the upper sequence number limit
            Interlocked.CompareExchange(ref packetID, 0, UInt32.MaxValue);
            // Increment and fetch the current sequence number
            packet.ID = (uint)Interlocked.Increment(ref packetID);

            UDPPacketBuffer buffer = new UDPPacketBuffer(base.remoteEndPoint);

            // TODO: Queue up outgoing commands and batch together into 1500 byte packets

            int pos = 0;
            // Serialize the packet ID
            DataConverter.BigEndian.PutBytes(buffer.Data, 0, packet.ID);
            pos += 4;
            // Serialize the command ID
            buffer.Data[pos++] = (byte)packet.Command.ID;
            // Serialize the command data
            packet.Command.Serialize(buffer.Data, ref pos);
            buffer.DataLength = pos;
            // Encrypt the command
            EncryptCommand(packet.ID, buffer, 4, pos - 4);

            base.AsyncBeginSend(buffer);
        }

        protected void EncryptCommand(uint packetID, UDPPacketBuffer buffer, int startPos, int size)
        {
            int pos = xorKey[packetID % VerseConstants.ENCRYPTION_KEY_SIZE] + startPos;

            for (int i = 0; i < size; i++)
                buffer.Data[startPos + i] ^= xorKey[(i + pos) % VerseConstants.ENCRYPTION_KEY_SIZE];
        }

        protected void DecryptPacket(UDPPacketBuffer buffer)
        {
            uint packetID = DataConverter.BigEndian.GetUInt32(buffer.Data, 0);
            int pos = xorKey[packetID % VerseConstants.ENCRYPTION_KEY_SIZE];

            for (int i = 4; i < buffer.DataLength; i++)
                buffer.Data[i] ^= xorKey[(i + pos) % VerseConstants.ENCRYPTION_KEY_SIZE];
        }

        protected void SendAck(uint packetID)
        {
            //LogAdapter.Debug("Acking packet " + packetID.ToString());

            PacketAckCommand ack = new PacketAckCommand();
            ack.PacketID = packetID;
            SendCommand(ack);
        }

        protected override void PacketSent(UDPPacketBuffer buffer, int bytesSent)
        {
        }

        protected override void PacketReceived(UDPPacketBuffer buffer)
        {
            DecryptPacket(buffer);

            int pos = 0;
            VersePacket packet = new VersePacket();
            packet.ID = DataConverter.BigEndian.GetUInt32(buffer.Data, pos);
            pos += 4;

            if (xorKeySet)
                SendAck(packet.ID);

            while (pos < buffer.DataLength)
            {
                CommandID commandID = (CommandID)buffer.Data[pos++];
                if (!DecodeCommand(packet.ID, commandID, buffer, ref pos))
                    break;
            }
        }

        private bool DecodeCommand(uint packetID, CommandID commandID, UDPPacketBuffer buffer, ref int pos)
        {
			if (commandID != CommandID.PacketAck && commandID != CommandID.PacketNak)
				Console.WriteLine(String.Format("Packet: {0} . Command: {1}", packetID, commandID));
            switch (commandID)
            {
                // System commands
                case CommandID.Connect:
                    ConnectCommand connect = new ConnectCommand();
                    connect.Deserialize(buffer.Data, ref pos);

                    if (connect.Stage == 0)
                    {
                        if (OnConnect0 != null)
                            OnConnect0(connect.Exponent, connect.Modulus);
                    }
                    else if (connect.Stage == 1)
                    {
                        if (OnConnect1 != null)
                            OnConnect1(connect.Version, connect.TimeSeconds, connect.TimeFractions, connect.Exponent, connect.Modulus);
                    }
                    else if (connect.Stage == 2)
                    {
                        if (OnConnect2 != null)
                            OnConnect2(connect.EncryptedData);
                    }

                    break;
                case CommandID.ConnectAccept:
                    ConnectAcceptCommand accept = new ConnectAcceptCommand();
                    accept.Deserialize(buffer.Data, ref pos);
                    if (OnConnectAccept != null)
                        OnConnectAccept(accept.AvatarID, accept.EncryptedXORKey);
                    break;
				case CommandID.ConnectTerminate:
					ConnectTerminateCommand terminate = new ConnectTerminateCommand();
					terminate.Deserialize(buffer.Data, ref pos);
					// TODO handle properly in server usage (resource freeing etc)
					if (OnConnectTerminate != null)
						OnConnectTerminate(terminate.Message);
					break;
                case CommandID.PacketAck:
                    PacketAckCommand ack = new PacketAckCommand();
                    ack.Deserialize(buffer.Data, ref pos);
                    if (OnPacketAck != null)
                        OnPacketAck(ack.PacketID);
                    break;
                case CommandID.PacketNak:
                    PacketNakCommand nak = new PacketNakCommand();
                    nak.Deserialize(buffer.Data, ref pos);
                    if (OnPacketNak != null)
                        OnPacketNak(nak.PacketID);
                    break;
                case CommandID.NodeIndexSubscribe:
                    NodeIndexSubscribeCommand indexSubscribe = new NodeIndexSubscribeCommand();
                    indexSubscribe.Deserialize(buffer.Data, ref pos);
                    if (OnNodeIndexSubscribe != null)
                        OnNodeIndexSubscribe(indexSubscribe.Mask);
                    break;
                case CommandID.NodeCreate:
                    NodeCreateCommand nodeCreate = new NodeCreateCommand();
                    nodeCreate.Deserialize(buffer.Data, ref pos);
					if (!nodeCreate.Destroy)
					{
						if (OnNodeCreate != null)
							OnNodeCreate(nodeCreate.NodeID, nodeCreate.NodeType, nodeCreate.NodeOwner);
					}
					else
					{
						if (OnNodeDestroy != null)
							OnNodeDestroy(nodeCreate.NodeID);
					}
                    break;
                case CommandID.NodeNameSet:
                    NodeNameSetCommand nodeNameSet = new NodeNameSetCommand();
                    nodeNameSet.Deserialize(buffer.Data, ref pos);
                    if (OnNodeNameSet != null)
                        OnNodeNameSet(nodeNameSet.NodeID, nodeNameSet.NodeName);
                    break;
                case CommandID.GeometryLayerCreate:
                    GeometryLayerCreateCommand geometryCreate = new GeometryLayerCreateCommand();
                    geometryCreate.Deserialize(buffer.Data, ref pos);
                    if (OnGeometryLayerCreate != null)
                        OnGeometryLayerCreate(geometryCreate.NodeID, geometryCreate.LayerID, geometryCreate.LayerName,
                            geometryCreate.LayerType, geometryCreate.DefaultInteger, geometryCreate.DefaultReal);
                    break;
				case CommandID.GeometryLayerSubscribe:
					GeometryLayerSubscribeCommand geometryLayerSubscribe = new GeometryLayerSubscribeCommand();
					geometryLayerSubscribe.Deserialize(buffer.Data, ref pos);
					if (geometryLayerSubscribe.Subscribed)
					{
						if (OnGeometryLayerSubscribe != null)
							OnGeometryLayerSubscribe(geometryLayerSubscribe.NodeID, geometryLayerSubscribe.LayerID, geometryLayerSubscribe.Precision);
					}
					else
					{
						if (OnGeometryLayerUnsubscribe != null)
							OnGeometryLayerUnsubscribe(geometryLayerSubscribe.NodeID, geometryLayerSubscribe.LayerID, geometryLayerSubscribe.Precision);
					}
					break;
                case CommandID.GeometryVertexSetXYZReal32:
                    GeometryVertexSetXYZReal32Command vertexSetXYZReal32 = new GeometryVertexSetXYZReal32Command();
                    vertexSetXYZReal32.Deserialize(buffer.Data, ref pos);
                    if (OnGeometryVertexSetXYZReal32 != null)
                        OnGeometryVertexSetXYZReal32(vertexSetXYZReal32.NodeID, vertexSetXYZReal32.LayerID, vertexSetXYZReal32.VertexID,
                            vertexSetXYZReal32.X, vertexSetXYZReal32.Y, vertexSetXYZReal32.Z);
                    break;
                case CommandID.GeometryPolygonSetCornerUInt32:
                    GeometryPolygonSetCornerUInt32Command polygonSetCornerUInt32 = new GeometryPolygonSetCornerUInt32Command();
                    polygonSetCornerUInt32.Deserialize(buffer.Data, ref pos);
                    if (OnGeometryPolygonSetCornerUInt32 != null)
                        OnGeometryPolygonSetCornerUInt32(polygonSetCornerUInt32.NodeID, polygonSetCornerUInt32.LayerID, polygonSetCornerUInt32.PolygonID,
                            polygonSetCornerUInt32.V0, polygonSetCornerUInt32.V1, polygonSetCornerUInt32.V2, polygonSetCornerUInt32.V3);
                    break;
                case CommandID.GeometryCreaseSetVertex:
                    GeometryCreaseSetVertexCommand geometryCreaseSetVertex = new GeometryCreaseSetVertexCommand();
                    geometryCreaseSetVertex.Deserialize(buffer.Data, ref pos);
                    if (OnGeometryCreaseSetVertex != null)
                        OnGeometryCreaseSetVertex(geometryCreaseSetVertex.NodeID, geometryCreaseSetVertex.LayerName, geometryCreaseSetVertex.DefaultCrease);
                    break;
                case CommandID.GeometryCreaseSetEdge:
                    GeometryCreaseSetEdgeCommand geometryCreaseSetEdge = new GeometryCreaseSetEdgeCommand();
                    geometryCreaseSetEdge.Deserialize(buffer.Data, ref pos);
                    if (OnGeometryCreaseSetEdge != null)
                        OnGeometryCreaseSetEdge(geometryCreaseSetEdge.NodeID, geometryCreaseSetEdge.LayerName, geometryCreaseSetEdge.DefaultCrease);
                    break;
				case CommandID.ObjectTransformSubscribe:
					ObjectTransformSubscribeCommand objectTransformSubscribe = new ObjectTransformSubscribeCommand();
					objectTransformSubscribe.Deserialize(buffer.Data, ref pos);
					if (OnObjectTransformSubscribe != null)
						OnObjectTransformSubscribe(objectTransformSubscribe.NodeID, objectTransformSubscribe.Precision, objectTransformSubscribe.Subscribed);
					break;
				case CommandID.ObjectTransformPosReal32:
					ObjectTransformPosReal32Command objectTransformPosReal32 = new ObjectTransformPosReal32Command();
					objectTransformPosReal32.Deserialize(buffer.Data, ref pos);
					if (OnObjectTransformPosReal32 != null)
						OnObjectTransformPosReal32(objectTransformPosReal32.NodeID, objectTransformPosReal32.TimeS, objectTransformPosReal32.TimeF, objectTransformPosReal32.Mask, objectTransformPosReal32.Pos, objectTransformPosReal32.Speed, objectTransformPosReal32.Accel, objectTransformPosReal32.DragNormal, objectTransformPosReal32.Drag);
					break;
				case CommandID.ObjectTransformRotReal32:
					ObjectTransformRotReal32Command objectTransformRotReal32 = new ObjectTransformRotReal32Command();
					objectTransformRotReal32.Deserialize(buffer.Data, ref pos);
					if (OnObjectTransformRotReal32 != null)
						OnObjectTransformRotReal32(objectTransformRotReal32.NodeID, objectTransformRotReal32.TimeS, objectTransformRotReal32.TimeF, objectTransformRotReal32.Mask, objectTransformRotReal32.Rot, objectTransformRotReal32.Speed, objectTransformRotReal32.Accel, objectTransformRotReal32.DragNormal, objectTransformRotReal32.Drag);
					break;
				case CommandID.ObjectTransformScaleReal32:
					ObjectTransformScaleReal32Command objectTransformScaleReal32 = new ObjectTransformScaleReal32Command();
					objectTransformScaleReal32.Deserialize(buffer.Data, ref pos);
					if (OnObjectTransformScaleReal32 != null)
						OnObjectTransformScaleReal32(objectTransformScaleReal32.NodeID, objectTransformScaleReal32.ScaleX, objectTransformScaleReal32.ScaleY, objectTransformScaleReal32.ScaleZ);
					break;
				case CommandID.ObjectLightSet:
					ObjectLightSetCommand objectLightSet = new ObjectLightSetCommand();
					objectLightSet.Deserialize(buffer.Data, ref pos);
					if (OnObjectLightSet != null)
						OnObjectLightSet(objectLightSet.NodeID, objectLightSet.R, objectLightSet.G, objectLightSet.B);
					break;
				case CommandID.ObjectLinkSet:
					ObjectLinkSetCommand objectLinkSet = new ObjectLinkSetCommand();
					objectLinkSet.Deserialize(buffer.Data, ref pos);
					if (OnObjectLinkSet != null)
						OnObjectLinkSet(objectLinkSet.NodeID, objectLinkSet.LinkID, objectLinkSet.Link, objectLinkSet.Label, objectLinkSet.TargetID, objectLinkSet.Set);
					break;
				case CommandID.ObjectMethodGroupCreate:
					ObjectMethodGroupCreateCommand objectMethodGroupCreate = new ObjectMethodGroupCreateCommand();
					objectMethodGroupCreate.Deserialize(buffer.Data, ref pos);
					if (OnObjectMethodGroupCreate != null)
						OnObjectMethodGroupCreate(objectMethodGroupCreate.NodeID, objectMethodGroupCreate.GroupID, objectMethodGroupCreate.Name);
					break;
				case CommandID.ObjectMethodGroupSubscribe:
					ObjectMethodGroupSubscribeCommand objectMethodGroupSubscribe = new ObjectMethodGroupSubscribeCommand();
					objectMethodGroupSubscribe.Deserialize(buffer.Data, ref pos);
					if (OnObjectMethodGroupSubscribe != null)
						OnObjectMethodGroupSubscribe(objectMethodGroupSubscribe.NodeID, objectMethodGroupSubscribe.GroupID, objectMethodGroupSubscribe.Subscribed);
					break;
				case CommandID.ObjectMethodCreate:
					ObjectMethodCreateCommand objectMethodCreate = new ObjectMethodCreateCommand();
					objectMethodCreate.Deserialize(buffer.Data, ref pos);
					if (objectMethodCreate.Destroy)
					{
						if (OnObjectMethodDestroy != null)
							OnObjectMethodDestroy(objectMethodCreate.NodeID, objectMethodCreate.GroupID, objectMethodCreate.MethodID);
					}
					else
					{
						if (OnObjectMethodCreate != null)
							OnObjectMethodCreate(objectMethodCreate.NodeID, objectMethodCreate.GroupID, objectMethodCreate.MethodID, objectMethodCreate.Name, objectMethodCreate.Parameters);
					}
					break;
				case CommandID.ObjectMethodCall:
					ObjectMethodCallCommand objectMethodCall = new ObjectMethodCallCommand();
					objectMethodCall.Deserialize(buffer.Data, ref pos);
					if (OnObjectMethodCall != null)
						OnObjectMethodCall(objectMethodCall.NodeID, objectMethodCall.GroupID, objectMethodCall.MethodID, objectMethodCall.SenderID, objectMethodCall.PackedParams);
					break;
				case CommandID.ObjectHide:
					ObjectHideCommand objectHide = new ObjectHideCommand();
					objectHide.Deserialize(buffer.Data, ref pos);
					if (OnObjectHide != null)
						OnObjectHide(objectHide.NodeID, objectHide.Hidden);
					break;
                default:
                    LogAdapter.Warn("Unrecognized packet command: " + commandID.ToString());
                    return false;
            }

            return true;
        }

        private void PacketAckHandler(uint packetID)
        {
            // TODO:
            //LogAdapter.Debug("Received a PacketAck for " + packetID.ToString());
        }

        private void PacketNakHandler(uint packetID)
        {
            // TODO:
            //LogAdapter.Debug("Received a PacketNak for " + packetID.ToString());
        }

        private void Connect1Handler(byte version, uint timeSeconds, uint timeFractions, byte[] exponent, byte[] modulus)
        {
            if (version != VerseConstants.VERSE_VERSION)
            {
                LogAdapter.Error(String.Format("Server is using version {0} of the verse protocol", version));
                return;
            }

            // Create a buffer for storing the username/password and initialize it with random data
            byte[] namePass = new byte[VerseConstants.ENCRYPTION_KEY_SIZE];
            Random rand = new Random();
            rand.NextBytes(namePass);

            // Copy the username and password into the buffer
            DataConverter.BigEndian.PutBytes(namePass, 0, username);
            DataConverter.BigEndian.PutBytes(namePass, 32, password);
            // Make sure the last byte is zero (required by the protocol)
            namePass[namePass.Length - 1] = 0;

            // Encrypt the username/password data
            RSAParameters rsaParams = new RSAParameters();
            rsaParams.Exponent = exponent;
            rsaParams.Modulus = modulus;
            RSAManaged rsa = new RSAManaged(512);
            rsa.ImportParameters(rsaParams);
            namePass = rsa.EncryptValue(namePass);

            // Send the stage 2 connection command (login)
            ConnectCommand connect = new ConnectCommand();
            connect.Stage = 2;
            connect.EncryptedData = namePass;
            SendCommand(connect);
        }

        private void ConnectAcceptHandler(uint avatarID, byte[] encryptedXORKey)
        {
            LogAdapter.Info("Connection accepted, AvatarID=" + avatarID.ToString());
            this.avatarID = avatarID;

            // Decrypt the XOR key
            xorKey = loginRSA.DecryptValue(encryptedXORKey);
            xorKeySet = true;
        }
    }
}
